/************************************************************************
 *
 * Copyright 2010 - 2012 Jakob Leben (jakob.leben@gmail.com)
 *
 * This file is part of SuperCollider Qt GUI.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ************************************************************************/

#include "type_codec.hpp"
#include "metatype.hpp"

#include "QObjectProxy.h"
#include "widgets/QcTreeWidget.h"
#include "primitives/prim_QPalette.hpp"
#include "image.h"

#include <PyrObject.h>
#include <PyrKernel.h>
#include <GC.h>
#include <VMGlobals.h>

#include <qmath.h>

namespace QtCollider {


QString TypeCodec<QString>::read(PyrSlot* slot) {
    if (IsSym(slot)) {
        return QString::fromUtf8(slotRawSymbol(slot)->name);
    } else if (isKindOfSlot(slot, class_string)) {
        int len = slotRawObject(slot)->size;
        return QString::fromUtf8(slotRawString(slot)->s, len);
    }
    return QString();
}


void TypeCodec<QString>::write(PyrSlot* slot, const QString& val) {
    PyrString* str = newPyrString(gMainVMGlobals->gc, val.toUtf8().constData(), 0, true);
    SetObject(slot, str);
}


QUrl TypeCodec<QUrl>::read(PyrSlot* slot) {
    if (IsSym(slot)) {
        return QUrl(QString::fromUtf8(slotRawSymbol(slot)->name));
    } else if (isKindOfSlot(slot, class_string)) {
        int len = slotRawObject(slot)->size;
        return QUrl(QString::fromUtf8(slotRawString(slot)->s, len));
    } else {
        return QUrl();
    }
}


void TypeCodec<QUrl>::write(PyrSlot* slot, const QUrl& val) {
    PyrString* str = newPyrString(gMainVMGlobals->gc, val.toString(QUrl::None).toUtf8().constData(), 0, true);
    SetObject(slot, str);
}

QPointF TypeCodec<QPointF>::read(PyrSlot* slot) {
    PyrSlot* slots = slotRawObject(slot)->slots;
    float x, y;
    int err;
    err = slotFloatVal(slots + 0, &x);
    if (err)
        return QPointF();
    err = slotFloatVal(slots + 1, &y);
    if (err)
        return QPointF();
    return QPointF(x, y);
}


QPointF TypeCodec<QPointF>::safeRead(PyrSlot* slot) {
    if (isKindOfSlot(slot, SC_CLASS(Point)))
        return read(slot);
    else
        return QPointF();
}


void TypeCodec<QPointF>::write(PyrSlot* slot, const QPointF& pt) {
    PyrObject* obj = instantiateObject(gMainVMGlobals->gc, SC_CLASS(Point), 0, true, true);
    SetObject(slot, obj);

    PyrSlot* slots = obj->slots;
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 0, pt.x());
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 1, pt.y());
}


QRectF TypeCodec<QRectF>::read(PyrSlot* slot) {
    PyrSlot* slots = slotRawObject(slot)->slots;
    float bounds[4];
    for (int i = 0; i < 4; ++i) {
        int err = slotFloatVal(slots + i, &bounds[i]);
        if (err)
            return QRectF();
    }

    return QRectF(bounds[0], bounds[1], bounds[2], bounds[3]);
}


QRectF TypeCodec<QRectF>::safeRead(PyrSlot* slot) {
    if (isKindOfSlot(slot, SC_CLASS(Rect)))
        return read(slot);
    else
        return QRectF();
}


void TypeCodec<QRectF>::write(PyrSlot* slot, const QRectF& r) {
    PyrObject* obj = instantiateObject(gMainVMGlobals->gc, SC_CLASS(Rect), 0, true, true);
    SetObject(slot, obj);

    PyrSlot* slots = obj->slots;
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 0, r.x());
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 1, r.y());
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 2, r.width());
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 3, r.height());
}


QSizeF TypeCodec<QSizeF>::read(PyrSlot* slot) {
    PyrSlot* slots = slotRawObject(slot)->slots;
    float w = 0.f, h = 0.f;
    slotFloatVal(slots + 0, &w);
    slotFloatVal(slots + 1, &h);

    return QSizeF(w, h);
}


QSizeF TypeCodec<QSizeF>::safeRead(PyrSlot* slot) {
    if (isKindOfSlot(slot, SC_CLASS(Size)))
        return read(slot);
    else
        return QSizeF();
}


void TypeCodec<QSizeF>::write(PyrSlot* slot, const QSizeF& sz) {
    PyrObject* obj = instantiateObject(gMainVMGlobals->gc, SC_CLASS(Size), 0, true, true);
    SetObject(slot, obj);

    PyrSlot* slots = obj->slots;
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 0, sz.width());
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 1, sz.height());
}

inline QColor asColor(PyrObject* obj) {
    PyrSlot* slots = obj->slots;

    float r, g, b, a;
    r = g = b = a = 0.f;
    slotFloatVal(slots + 0, &r);
    slotFloatVal(slots + 1, &g);
    slotFloatVal(slots + 2, &b);
    slotFloatVal(slots + 3, &a);
    return QColor(r * 255, g * 255, b * 255, a * 255);
}


QColor TypeCodec<QColor>::read(PyrSlot* slot) {
    PyrObject* obj = slotRawObject(slot);
    PyrClass* klass = obj->classptr;

    if (klass == SC_CLASS(Color))
        return asColor(obj);

    if (klass == SC_CLASS(Gradient) || klass == SC_CLASS(HiliteGradient)) {
        qcWarningMsg("WARNING: Gradient and HiliteGradient are not supported yet."
                     " Using the average gradient color instead.");

        QColor c1(safeRead(obj->slots + 0));
        QColor c2(safeRead(obj->slots + 1));
        QColor mix((c1.red() + c2.red()) / 2, (c1.green() + c2.green()) / 2, (c1.blue() + c2.blue()) / 2);
        return mix;
    }

    return QColor();
}


void TypeCodec<QColor>::write(PyrSlot* slot, const QColor& c) {
    if (!c.isValid()) {
        SetNil(slot);
        return;
    }

    PyrObject* obj = instantiateObject(gMainVMGlobals->gc, SC_CLASS(Color), 0, true, true);
    SetObject(slot, obj);

    PyrSlot* slots = obj->slots;
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 0, c.red() / 255.0);
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 1, c.green() / 255.0);
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 2, c.blue() / 255.0);
    SetFloat<AssertDouble::CouldBeBadNan>(slots + 3, c.alpha() / 255.0);
}


QFont TypeCodec<QFont>::read(PyrSlot* slot) {
    PyrSlot* slots = slotRawObject(slot)->slots;

    QString family = TypeCodec<QString>::safeRead(slots + 0);
    float fSize = TypeCodec<float>::safeRead(slots + 1);
    bool bold = TypeCodec<bool>::safeRead(slots + 2);
    bool italic = TypeCodec<bool>::safeRead(slots + 3);
    bool isPtSize = TypeCodec<bool>::safeRead(slots + 4);

    QFont f;

    if (!family.isEmpty())
        f.setFamily(family);

    if (fSize > 0.f) {
        if (isPtSize) {
            f.setPointSizeF(fSize);
        } else {
            int pixSize = (fSize > 1.f ? qRound(fSize) : 1);
            f.setPixelSize(pixSize);
        }
    }

    f.setBold(bold);

    f.setItalic(italic);

    return f;
}


QFont TypeCodec<QFont>::safeRead(PyrSlot* slot) {
    if (isKindOfSlot(slot, SC_CLASS(Font)))
        return TypeCodec<QFont>::read(slot);
    else
        return QFont();
}

QPalette TypeCodec<QPalette>::read(PyrSlot* slot) {
    QPalette* p = QPALETTE_FROM_OBJECT(slotRawObject(slot));
    return *p;
}


QPalette TypeCodec<QPalette>::safeRead(PyrSlot* slot) {
    if (isKindOfSlot(slot, SC_CLASS(QPalette)))
        return TypeCodec<QPalette>::read(slot);
    else
        return QPalette();
}


void TypeCodec<QPalette>::write(PyrSlot* slot, const QPalette& plt) {
    PyrGC* gc = gMainVMGlobals->gc;
    PyrObject* obj = instantiateObject(gc, SC_CLASS(QPalette), 0, true, true);
    SetObject(slot, obj);

    QPalette_Init(gMainVMGlobals, obj, plt);
}


QObjectProxy* TypeCodec<QObjectProxy*>::safeRead(PyrSlot* slot) {
    if (!isKindOfSlot(slot, SC_CLASS(QObject)))
        return 0;
    return read(slot);
}

void TypeCodec<QObject*>::write(PyrSlot* slot, QObject* obj) {
    QObjectProxy* proxy = QObjectProxy::fromObject(obj);

    if (proxy && proxy->scObject())
        SetObject(slot, proxy->scObject());
    else
        SetNil(slot);
}


QVariantList TypeCodec<QVariantList>::read(PyrSlot* slot) {
    if (isKindOfSlot(slot, class_array)) {
        PyrObject* obj = slotRawObject(slot);
        PyrSlot* slots = obj->slots;
        int size = obj->size;
        QVariantList list;
        for (int i = 0; i < size; ++i, ++slots) {
            list << QtCollider::get<QVariant>(slots);
        }
        return list;
    } else if (isKindOfSlot(slot, class_symbolarray)) {
        PyrSymbolArray* symarray = slotRawSymbolArray(slot);
        PyrSymbol** symbols = symarray->symbols;
        int size = symarray->size;
        QVariantList list;
        for (int i = 0; i < size; ++i, ++symbols)
            list << QVariant(QString((*symbols)->name));
        return list;
    }

    return QVariantList();
}


void TypeCodec<QVariantList>::write(PyrSlot* slot, const QVariantList& varList) {
    VMGlobals* g = gMainVMGlobals;

    int count = varList.count();

    PyrObject* array = newPyrArray(g->gc, count, 0, true);
    SetObject(slot, array);

    int i;
    PyrSlot* s = array->slots;
    for (i = 0; i < count; ++i, ++s) {
        if (!QtCollider::set(s, varList[i])) {
            qcDebugMsg(1, "WARNING: Could not set one slot of array");
        }
        array->size++;
        g->gc->GCWrite(array, s);
    }
}


#define WRONG_OBJECT_FORMAT false

template <typename DEST, typename ORIG> inline static void copy(QVector<DEST>& dest, PyrSlot* orig, int size) {
    ORIG* array = (ORIG*)orig;
    for (int i = 0; i < size; ++i)
        dest << DEST(array[i]);
}

template <typename numeric_type> static QVector<numeric_type> toNumericVector(PyrObject* obj) {
    int size = obj->size;
    PyrSlot* slots = obj->slots;

    QVector<numeric_type> vector;
    vector.reserve(size);

    switch (obj->obj_format) {
    case obj_double:
        copy<numeric_type, double>(vector, slots, size);
        break;
    case obj_float:
        copy<numeric_type, float>(vector, slots, size);
        break;
    case obj_int32:
        copy<numeric_type, int32>(vector, slots, size);
        break;
    case obj_int16:
        copy<numeric_type, int16>(vector, slots, size);
        break;
    case obj_int8:
        copy<numeric_type, int8>(vector, slots, size);
        break;
    default:
        Q_ASSERT(WRONG_OBJECT_FORMAT);
    }

    return vector;
}

template <typename numeric_type> static void setNumeric(PyrSlot*, numeric_type);


template <> inline void setNumeric<double>(PyrSlot* s, double val) { SetFloat(s, val); }

template <> inline void setNumeric<int>(PyrSlot* s, int val) { SetInt(s, val); }

template <typename numeric_type> static void setNumericVector(PyrSlot* slot, const QVector<numeric_type>& vec) {
    VMGlobals* g = gMainVMGlobals;

    int count = vec.count();

    PyrObject* array = newPyrArray(g->gc, count, 0, true);
    SetObject(slot, array);

    PyrSlot* s = array->slots;
    Q_FOREACH (numeric_type val, vec) {
        setNumeric<numeric_type>(s, val);
        ++array->size;
        ++s;
    }
}

QVector<double> TypeCodec<QVector<double>>::read(PyrSlot* slot) { return toNumericVector<double>(slotRawObject(slot)); }


void TypeCodec<QVector<double>>::write(PyrSlot* slot, const QVector<double>& vec) {
    setNumericVector<double>(slot, vec);
}


QVector<int> TypeCodec<QVector<int>>::read(PyrSlot* slot) { return toNumericVector<int>(slotRawObject(slot)); }


void TypeCodec<QVector<int>>::write(PyrSlot* slot, const QVector<int>& vec) { setNumericVector<int>(slot, vec); }

QcTreeWidget::ItemPtr TypeCodec<QcTreeWidget::ItemPtr>::read(PyrSlot* slot) {
    PyrSlot* ptrSlot = slotRawObject(slot)->slots + 0;
    if (IsPtr(ptrSlot)) {
        QcTreeWidget::ItemPtr* safePtr = static_cast<QcTreeWidget::ItemPtr*>(slotRawPtr(ptrSlot));
        return *safePtr;
    } else {
        return QcTreeWidget::ItemPtr();
    }
}


void TypeCodec<QcTreeWidget::ItemPtr>::write(PyrSlot* slot, const QcTreeWidget::ItemPtr& item) {
    PyrObject* obj = instantiateObject(gMainVMGlobals->gc, SC_CLASS(TreeViewItem), 0, true, true);
    QcTreeWidget::Item::initialize(gMainVMGlobals, obj, item);
    SetObject(slot, obj);
}

SharedImage TypeCodec<SharedImage>::read(PyrSlot* slot) {
    SharedImage* ptr = reinterpret_cast<SharedImage*>(slotRawPtr(slotRawObject(slot)->slots + 0));
    return *ptr;
}

SharedImage TypeCodec<SharedImage>::safeRead(PyrSlot* slot) {
    if (!isKindOfSlot(slot, SC_CLASS(Image)))
        return SharedImage();
    else
        return read(slot);
}

void TypeCodec<SharedImage>::write(PyrSlot* slot, SharedImage image) {
    qWarning("WARNING: QtCollider: writing SharedImage to PyrSlot not supported.");
}

}

// namespace QtCollider
